C++异常设计目的是将程序中检测到错误与处理的错误区分开，这个概念非常适合SYCL中的同步和异步错误。通过抛出和捕获机制，可以定义处理程序的层次。\par

构建能以可靠的方式处理错误的应用程序，需要预先制定策略，并为错误处理构建相应的体系。C++提供了工具来实现策略，但是这样的架构超出了本章讨论的范围。有许多书籍和参考资料专门对这个主题进行讨论，有兴趣可以去全面的了解一下C++错误处理的策略。\par

错误检测和报告与程序实际功能并不相关。如果目标仅仅是在执行过程中检测并报告错误(但不一定是从错误中恢复)，可以通过可靠地检测和报告程序中的错误。下面几节先来介绍忽略错误处理时会发生什么(默认行为并不是那么糟糕!)，然后介绍在应用程序中容易实现的错误报告方式。\par

\hspace*{\fill} \par %插入空行
\textbf{忽略错误}

在C++和SYCL中即使没有显式地处理错误，也会出现错误。未处理的同步或异步错误的默认结果是程序异常终止。下面两个示例分别模拟了不处理同步错误和异步错误的情况。\par

图5-4展示了不处理的C++异常的情况，例如：该异常可能是未处理的SYCL同步错误。可以使用此代码来测试特定操作系统在这种情况下报告什么错误。\par

图5-5展示了std::terminate的输出，这是程序中未处理的SYCL异步错误的情况。可以使用此代码来测试特定操作系统在这种情况下将报告什么错误。\par

因为未捕获的错误将使程序终止，所以需要对错误进行处理!\par

\hspace*{\fill} \par %插入空行
图5-4 不处理异常
\begin{lstlisting}[caption={}]
#include <iostream>

class something_went_wrong {};

int main() {
	std::cout << "Hello\n";
	
	throw(something_went_wrong{});
}
/*
Example output in Linux:
Hello
terminate called after throwing an instance of 'something_went_wrong'

Aborted (core dumped)
*/
\end{lstlisting}

\hspace*{\fill} \par %插入空行
图5-5 不处理SYCL异步异常时，将调用std::terminate
\begin{lstlisting}[caption={}]
#include <iostream>

int main() {
	std::cout << "Hello\n";
	
	std::terminate();
}
/*
Example output in Linux:
Hello
terminate called without an active exception
Aborted (core dumped)
*/
\end{lstlisting}

\hspace*{\fill} \par %插入空行
\textbf{同步的处理错误}

SYCL同步错误与C++异常一样，SYCL中添加的大多错误机制都与异步错误有关，我们将在下一节中介绍异步错误，而同步错误非常重要，因为实现会以同步的方式检测和报告尽可能多的错误，使其更容易检测和处理。\par

SYCL定义的同步错误是SYCL::exception类型，是std::exception的派生类，其允许通过如图5-6所示的try-catch来捕获异常。\par

\hspace*{\fill} \par %插入空行
图5-6 捕获sycl::exception的模式
\begin{lstlisting}[caption={}]
try{
	// Do some SYCL work
} catch (sycl::exception &e) {
	// Do something to output or handle the exceptinon 
	std::cout << "Caught sync SYCL exception: " << e.what() << "\n";
	return 1;
} 
\end{lstlisting}

在C++处理错误机制的基础上，SYCL添加了SYCL::exception异常类型。其他与标准的C++异常处理方式相同，也符合大多数开发者的习惯。\par

图5-7提供了一个处理异常的示例，并通过从main()来结束程序。\par

\hspace*{\fill} \par %插入空行
图5-7 捕获异常
\begin{lstlisting}[caption={}]
try{
	buffer<int> B{ range{16} };
	// ERROR: Create sub-buffer larger than size of parent buffer
	// An exception is thrown from within the buffer constructor
	buffer<int> B2(B, id{8}, range{16});
	
} catch (sycl::exception &e) {
	// Do something to output or handle the exception 
	std::cout << "Caught sync SYCL exception: " << e.what() << "\n";
	return 1;
} catch (std::exception &e) {
	std::cout << "Caught std exception: " << e.what() << "\n";
	return 2;
} catch (...) {
	std::cout << "Caught unknown exception\n";
	return 3;
}
return 0;

/*
Example output:
Caught sync SYCL exception: Requested sub-buffer size exceeds the size of 
the parent buffer -30 (CL_INVALID_VALUE)
*/
\end{lstlisting}

\hspace*{\fill} \par %插入空行
\textbf{异步的错误处理}

异步错误由SYCL运行时(或底层后端)检测，错误的发生与主机程序无关。错误存储在SYCL运行时的内部，仅在开发者可以控制的点进行处理。我们需要对异步错误的处理进行讨论:\par

\begin{enumerate}
	\item \textbf{异步处理程序}，当有未处理的异步错误需要处理时调用
	\item 异步处理程序\textbf{何时}调用
\end{enumerate}

\hspace*{\fill} \par %插入空行
\textbf{异步处理程序}

异步处理程序是个函数定义，使用SYCL上下文和/或队列注册。下一节中，有未处理的异步异常需要处理，SYCL运行时将调用异步处理程序，并将其传递到异常列表中。\par

异步处理程序以std::function的形式传递给上下文或队列构造函数，可以根据常规函数、Lambda或函数操作符等方式定义。处理程序必须接受sycl::exception\_list参数，如图5-8所示的示例处理程序。\par

\hspace*{\fill} \par %插入空行
图5-8 定义为lambda的异步处理程序示例
\begin{lstlisting}[caption={}]
// Our simple asynchronous handler function
auto handle_async_error = [](exception_list elist) {
	for (auto &e : elist) {
		try{ std::rethrow_exception(e); }
		catch ( sycl::exception& e ) {
			std::cout << "ASYNC EXCEPTION!!\n";
			std::cout << e.what() << "\n";
		}
	}
};
\end{lstlisting}

图5-8中，std::rethrow\_exception可以抛出特定的异常类型，并可以使用catch对异常进行捕捉，本例中捕捉的是sycl::exception。还可以在C++中使用其他方法，或者选择处理所有类型的异常。\par

处理程序在构造时与队列或上下文(第6章将详细介绍底层细节)相关联。例如，将图5-8中定义的处理程序注册到正在创建的队列中，可以写成：\par

queue my\_queue{ gpu\_selector{}, handle\_async\_error };\par

同样，要将图5-8中定义的处理程序注册到正在创建的上下文中，可以写成:\par

context my\_context{ handle\_async\_error };\par

大多数应用程序不需要显式地创建或管理上下文(程序会自动创建)，大多数开发者应该为特定设备构造处理程序与队列(而不是显式创建上下文)。\par

\begin{tcolorbox}[colback=red!5!white,colframe=red!75!black]
应该在队列上定义异步处理程序时(除非已经显式的管理了上下文)。
\end{tcolorbox}

如果没有为队列或队列的父上下文定义异步处理程序，在处理队列(或上下文中)上发生的异步错误时，会使用默认的异步处理程序，就如图5-9所示。\par

\hspace*{\fill} \par %插入空行
图5-9 默认异步处理程序的示例
\begin{lstlisting}[caption={}]
// Our simple asynchronous handler function
auto handle_async_error = [](exception_list elist) {
	for (auto &e : elist) {
		try{ std::rethrow_exception(e); }
		catch ( sycl::exception& e ) {
			// Print information about the asynchronous exception
		}
	}

	// Terminate abnormally to make clear to user
	// that something unhandled happened
	std::terminate();
};
\end{lstlisting}

默认处理程序应该向用户显示异常列表中的错误信息，然后以非正常的方式终止应用程序，同时也需要让操作系统记录下这次非正常终止。\par

异步处理程序中的执行由我们来定，可以记录错误、终止程序、恢复错误，以便程序可以继续正常执行。常见的情况是通过sycl::exception::what()来展示错误的细节，然后终止程序。\par

虽然是我们决定异步处理程序做什么，但常见的错误是打印错误消息(会受程序中的其他消息的干扰)，然后完成处理函数。除非有适当的错误处理策略，允许恢复已知的程序状态，并确信继续执行的安全，否则应该考虑在异步处理程序函数中终止应用程序，以便减少了在检测到错误的程序中出现结果错误的可能性。许多程序中，当出现异步异常时，终止是首选。\par

\begin{tcolorbox}[colback=red!5!white,colframe=red!75!black]
如果没有完善的错误恢复和处理机制，请考虑在输出有关错误的信息之后，终止程序。
\end{tcolorbox}

\hspace*{\fill} \par %插入空行
\textbf{处理程序的调用}

运行时在特定时间调用异步处理程序。错误发生时不会立即报告，所以管理错误和编程(特别是多线程)将变得更加困难。异步处理程序会在以下特定时间调用:\par

\begin{enumerate}
	\item 主程序在特定队列上调用\textbf{queue::throw\_asynchronous()}时
	\item 主程序在特定队列上调用\textbf{queue::wait\_and\_throw()}时
	\item 主程序在特定事件上调用\textbf{event::wait\_and\_throw()}时
	\item 一个\textbf{队列}销毁时
	\item 一个\textbf{上下文}销毁时
\end{enumerate}

方法1-3提供了一种控制点机制，让主机程序控制何时处理异步异常，这样就可以管理线程安全和其他特定程序的细节。在控制点上，异步异常可以进入主程序控制流，并且可以像处理同步错误一样处理它们。\par

如果用户没有显式地调用方法1-3的其中一个，那么当队列和上下文销毁时，通常会在程序关闭时报告异步错误。这足以向用户发出错误的信号，并表示不要信任本次程序的最终结果。\par

不过，程序的正确性依赖错误检测，并不是在所有情况下都有效。例如，如果程序只在某些算法收敛条件达到时才会终止，而这些条件只有通过成功执行设备内核才能实现，那么某个异步异常可能会让算法永远不收敛。这种情况下，以及在有更完整的错误处理策略的生产应用程序中，程序中使用常规调用和控制点调用throw\_asynchronous()或wait\_and\_throw()都是有意义的(例如，在检查算法是否收敛之前调用)。\par



















